﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows;
using System.Windows.Media;
using Foundation;
using Foundation.ExtensionMethods;
using Foundation.Spell;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Editing;
using ICSharpCode.AvalonEdit.Highlighting;
using ICSharpCode.AvalonEdit.Rendering;
using Poet.ViewModels;

namespace Poet.Modules
{
    /// <summary>
    /// A colorizes that interprets a highlighting rule set and colors the document accordingly.
    /// </summary>
    public class SpellColorizer : DocumentColorizingTransformer, IDisposable
    {
        public static string[] Languages = new[] {"da", "de", "en", "es", "fi", "fr", "it", "nl", "pl", "pt", "sv"};

        private TextArea _textArea;

        public static readonly ObservableCollection<string> Words = new ObservableCollection<string>(); 

        public readonly ObservableCollection<SpellManager> UsedSpellCheckers = new ObservableCollection<SpellManager>();

        public static readonly List<SpellManager> SpellCheckers = new List<SpellManager>();

        public static List<SpellService> Services { get; set; }

        private static Pen ErrorPen { get; set; }

        private static TextDecoration ErrorDecoration { get; set; }

        private static TextDecorationCollection ErrorDecorations { get; set; }

        static SpellColorizer()
        {
            try
            {
                ErrorPen = CreateErrorPen();
                ErrorDecoration = new TextDecoration(TextDecorationLocation.Underline, ErrorPen, 0,
                                                     TextDecorationUnit.Pixel, TextDecorationUnit.Pixel);
                ErrorDecorations = new TextDecorationCollection {ErrorDecoration};
                var file = PathProvider.FindFileByName(Properties.Settings.Default.SpellSevices);
                if (!PathProvider.Exists(file))
                    File.WriteAllLines(file, new[] {Properties.Resources.SpellServices});

                Services = Serializer.Deserialize<List<SpellService>>(file);
                if (Services == null) return;
                foreach (var spellService in Services)
                {
                    SpellCheckers.Add(new SpellManager(spellService));
                }

                ViewModel.ApplicationClosing += () =>
                    {
                        foreach (var spellService in SpellCheckers)
                        {
                            var fileName =
                                string.IsNullOrEmpty(spellService.WebService.DictionaryFileName)
                                    ? spellService.WebService.Name + ".dic.txt"
                                    : spellService.WebService.DictionaryFileName;
                            fileName = PathProvider.FindFileByName(fileName);
                            if (Store.OfType<SettingsViewModel>().SpellCheckTeaching)
                                spellService.SaveDictionary(fileName);
                        }
                    };
            }
            catch (Exception exception)
            {
                MessageBox.Show(exception.Message + Environment.NewLine + exception.InnerException.Message,
                                "Spell Colorizer Exception");
            }
        }

        /// <summary>
        /// Creates a new HighlightingColorizer instance.
        /// </summary>
        /// <param name="textArea">The text area.</param>
        public SpellColorizer(TextArea textArea)
        {
            _textArea = textArea;
            SpellCheckers.ForEach(c => c.NewWordsChecked += OnNewWordsChecked);
            SpellCheckers.Where(c => c.WebService.Use).ForEach(c => UsedSpellCheckers.Add(c));
            UsedSpellCheckers.CollectionChanged += (sender, args) => RaiseUpdatedEvent();
        }

        public void Dispose()
        {
            _textArea = null;
            SpellCheckers.ForEach(c => c.NewWordsChecked -= OnNewWordsChecked);
        }

        private void OnNewWordsChecked(SpellManager spellChecker, string[] newWords)
        {
            if (UsedSpellCheckers.Contains(spellChecker))
                Application.Current.Dispatcher.BeginInvoke((ThreadStart) RaiseUpdatedEvent);
        }

        private static void textView_DocumentChanged(object sender, EventArgs e)
        {
            OnDocumentChanged((TextView) sender);
        }

        private static void OnDocumentChanged(TextView textView)
        {
            // remove existing highlighter, if any exists
            textView.Services.RemoveService(typeof (IHighlighter));
            textView.Services.RemoveService(typeof (DocumentHighlighter));
        }

        /// <inheritdoc/>
        protected override void OnAddToTextView(TextView textView)
        {
            base.OnAddToTextView(textView);
            textView.DocumentChanged += textView_DocumentChanged;
            OnDocumentChanged(textView);
        }

        /// <inheritdoc/>
        protected override void OnRemoveFromTextView(TextView textView)
        {
            base.OnRemoveFromTextView(textView);
            textView.Services.RemoveService(typeof (IHighlighter));
            textView.Services.RemoveService(typeof (DocumentHighlighter));
            textView.DocumentChanged -= textView_DocumentChanged;
        }

        /// <inheritdoc/>
        protected override void ColorizeLine(DocumentLine line)
        {
            if (_textArea == null || _textArea.Document == null || line == null) return;

            var text = _textArea.Document.Text.Substring(line.Offset, line.Length);
            if (string.IsNullOrEmpty(text)) return;

            var errors = new List<SpellAssotiation>();
            foreach (var checkerErrors in UsedSpellCheckers.Select(spellChecker => spellChecker.Check(text)))
            {
                errors.AddRange(checkerErrors.Where(e => errors.All(x => x.Offset != e.Offset)));
            }

            foreach (var spellChecker in SpellCheckers.Where(c => UsedSpellCheckers.Contains(c)))
            {
                spellChecker.Check(text);
            }

            var hl = new HighlightedLine(_textArea.Document, line);
            foreach (var error in errors)
            {
                var section = new HighlightedSection
                    {
                        Color = new HighlightingColor() /*{ Foreground = new SimpleHighlightingBrush(Colors.Gray)}*/,
                        Offset = error.Offset + line.Offset,
                        Length = error.Length,
                    };

                hl.Sections.Add(section);
                var section1 = section;
                ChangeLinePart(section.Offset, section.Offset + section.Length,
                               visualLineElement => ApplyColorToElement(visualLineElement, section1.Color));
            }

            //if (_textArea.Matches != null)
            //    foreach (var match in _textArea.Matches)
            //    {
            //        if (line.Offset <= match.Index && match.Index <= line.Offset + line.Length)
            //        {
            //            var section = new HighlightedSection
            //                {
            //                  //  Color = new HighlightingColor() {Background = new SimpleHighlightingBrush(_textArea.SelectionBrush)},
            //                    Offset = match.Index,
            //                    Length = match.Length,
            //                };

            //            hl.Sections.Add(section);
            //            var section1 = section;
            //            ChangeLinePart(section.Offset, section.Offset + section.Length,
            //                           visualLineElement => visualLineElement.TextRunProperties.SetBackgroundBrush(_textArea.SelectionBrush));
            //        }
            //    }
        }

        /// <summary>
        /// Applies a highlighting color to a visual line element.
        /// </summary>
        protected virtual void ApplyColorToElement(VisualLineElement element, HighlightingColor color)
        {
            if (color.Foreground != null)
            {
                var b = color.Foreground.GetBrush(CurrentContext);
                if (b != null)
                    element.TextRunProperties.SetForegroundBrush(b);
            }

            if (color.Background != null)
            {
                var b = color.Background.GetBrush(CurrentContext);
                if (b != null)
                    element.TextRunProperties.SetBackgroundBrush(b);
            }

            element.TextRunProperties.SetTextDecorations(ErrorDecorations);

            if (color.FontStyle != null || color.FontWeight != null)
            {
                var typeface = element.TextRunProperties.Typeface;
                element.TextRunProperties.SetTypeface(new Typeface(
                                                          typeface.FontFamily,
                                                          color.FontStyle ?? typeface.Style,
                                                          color.FontWeight ?? typeface.Weight,
                                                          typeface.Stretch));
            }
        }

        private static Pen CreateErrorPen()
        {
            var geometry = new StreamGeometry();
            using (var context = geometry.Open())
            {
                context.BeginFigure(new Point(0.0, 0.0), false, false);
                context.PolyLineTo(new[]
                    {
                        new Point(0.75, 0.75),
                        new Point(1.5, 0.0),
                        new Point(2.25, 0.75),
                        new Point(3.0, 0.0)
                    }, true, true);
            }

            var brushPattern = new GeometryDrawing
                {
                    Pen = new Pen(Brushes.Red, 0.2),
                    Geometry = geometry
                };

            var brush = new DrawingBrush(brushPattern)
                {
                    TileMode = TileMode.Tile,
                    Viewport = new Rect(0.0, 1.5, 9.0, 3.0),
                    ViewportUnits = BrushMappingMode.Absolute
                };

            var pen = new Pen(brush, 3.0);
            pen.Freeze();

            return pen;
        }
    }
}
